import ast
import os
import time
from _ast import FunctionDef
from typing import Dict, List, Tuple

from llvmlite import ir

from MelodieAllegro.type_scanner import query_function_ast, scan_funcdef, scan_class

from llvm_compile import run_func, run_f


def build_dependencies(
        file,
        extra_funcs_and_objects,
        code,
        classes,
        module,
        functions,

):
    """
    依次构建每一个被依赖的类和函数。

    如果有泛型，那么就针对每一个泛型，进行模板替换，同时生成一定格式的代码。
    如类型A具有泛型变量T 有int和float两种实际类型，则名称为

    A<T int>, A<T float>两种。

    当扫描到相关对象的类型时，也会根据泛型类型决定使用哪种泛型。

    目前使用了字符串替换的方式，可能不够稳。

    :param file:
    :param extra_funcs_and_objects:
    :param code:
    :param classes:
    :param module:
    :param functions:
    :return:
    """
    for dependency in extra_funcs_and_objects:
        dep_type = dependency[0]
        assert dep_type in {'function', 'class'}
        dep_name = dependency[1]
        generics: List[Dict[str, str]]
        if len(dependency) >= 3:
            generics = dependency[2]
        else:
            generics = [{'': ''}]
        if len(dependency) >= 4:
            filename = dependency[3]
        else:
            filename = file
        for generic_item in generics:

            if dep_type == 'function':
                with open(filename) as f:
                    file_code = f.read()
                func_ast: FunctionDef = query_function_ast(ast.parse(file_code), dep_name)
                f_def = scan_funcdef(func_ast, classes.copy(), None)
                f_def.create(module, classes=classes.copy(), global_functions=functions.copy())
                functions[dep_name] = f_def
            else:
                cls_def = scan_class(filename,
                                     dep_name,
                                     all_classes=classes,
                                     all_funcs=functions,
                                     generic_item=generic_item)

                classes[cls_def.name] = cls_def
                cls_def.create_type(module, functions=functions.copy(), classes=classes.copy())


class ObjectToExec:
    def __init__(self):
        from MelodieAllegro.types import PMObjectType, PMFunctionType
        self.ir: str = ""
        self.classes: Dict[str, PMObjectType] = {}
        self.functions: Dict[str, PMFunctionType] = {}
        self.module: ir.Module = None


def before_run(file: str,
               entry_func_name,
               extra_funcs_and_objects: List[Tuple[str, str]],
               ):
    from .exteral_functions.builtin_func_types import builtins
    from .types import PMObjectType
    assert os.path.exists(file)
    module = ir.Module(__file__)

    # 初始化builtins构建器内部的module模块。
    builtins.init(module)
    builtins.after_initiated(module)

    with open(file, encoding='utf-8') as f:
        code = f.read()

    functions = {}
    classes: Dict[str, PMObjectType] = {}

    build_dependencies(
        file,
        extra_funcs_and_objects,
        code,
        classes,
        module,
        functions,
    )

    func_ast = query_function_ast(ast.parse(code), entry_func_name)
    f_def = scan_funcdef(func_ast, classes.copy(), None)
    functions[f_def.name] = f_def
    f_def.create(module, global_functions=functions.copy(), classes=classes)

    _ir = repr(module)

    with open('temp.ll', 'w') as f:
        f.write(_ir)
    ote = ObjectToExec()
    ote.ir = _ir
    ote.classes = classes
    ote.functions = functions
    ote.module = module
    return ote


def multi_function_test_pipeline(file: str, entry_func_name,
                                 extra_funcs_and_objects: List[Tuple[str, str]],
                                 arg_types, ret_type, args):
    object_to_exec = before_run(file, entry_func_name, extra_funcs_and_objects)
    t0 = time.time()
    statistics = {}
    # run_f(object_to_exec.module);
    res = None
    res = run_func(object_to_exec.ir, entry_func_name,
                   arg_types, ret_type, args, statistics=statistics)
    t1 = time.time()

    return res, statistics['time']
